package org.apache.felix.dm.impl.dependencies.bi;

import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.felix.dm.Component;
import org.apache.felix.dm.Dependency;
import org.apache.felix.dm.DependencyManager;

@SuppressWarnings("rawtypes")
class AdapterCallbackHandler {

	private final Class<?>[] capabilityInterfaces;

	private final String[] triggerMethods;

	private final DependencyManager dependencyManager;

	private Map<Class, List<Dependency>> capabilityDependencies = new HashMap<Class, List<Dependency>>();

	private Map<Class, CallbacksDescription> capabilityCallbacks = new HashMap<Class, CallbacksDescription>();

	private Map<Class, Properties> capabilityServiceProperties = new HashMap<Class, Properties>();

	private List<Component> capabilityComponents = new ArrayList<Component>();

	private final Object callbackInstance;
	
	public AdapterCallbackHandler(DependencyManager dependencyManager, Class<?>[] capabilityInterfaces, String[] triggerMethods) {
		this(dependencyManager, capabilityInterfaces, triggerMethods, null);
	}

	public AdapterCallbackHandler(DependencyManager dependencyManager, Class<?>[] capabilityInterfaces, String[] triggerMethods, Object callbackInstance) {
		this.dependencyManager = dependencyManager;
		this.capabilityInterfaces = capabilityInterfaces;
		this.triggerMethods = triggerMethods;
		this.callbackInstance = callbackInstance;
	}

	void addCapabilityDepdendencies(Class capabilityInterface, Dependency... dependencies) {
		List<Dependency> dependencyList = capabilityDependencies.get(capabilityInterface);
		if (dependencyList == null) {
			dependencyList = new ArrayList<Dependency>();
			capabilityDependencies.put(capabilityInterface, dependencyList);
		}
		dependencyList.addAll(Arrays.asList(dependencies));
	}

	/**
	 * @param capabilityInterface
	 * @param properties
	 */
	public void setCapabilityServiceProperties(Class<?> capabilityInterface, Properties properties) {
		capabilityServiceProperties.put(capabilityInterface, properties);
	}

	/**
	 * @param capabilityInterface
	 * @param init
	 * @param start
	 * @param stop
	 * @param destroy
	 */
	public void setCapabilityCallbacks(Class<?> capabilityInterface, String init, String start, String stop, String destroy) {
		capabilityCallbacks.put(capabilityInterface, new CallbacksDescription(init, start, stop, destroy));
	}

	void init(Component component) {
		// create capability service proxies
		Map<String, Toggle> toggles = new HashMap<String, Toggle>();
		int pos = 0;
		List<Dependency> dependencies = new ArrayList<Dependency>();
		for (String targetMethod : triggerMethods) {
			Toggle toggle = new Toggle();
			toggles.put(targetMethod, toggle);
			dependencies.add(new CallbackDependency(toggle, targetMethod, true, true));
			pos++;
		}
		component.add(dependencies);

		// create the proxy for all capability interfaces
		Object implementation = Proxy.newProxyInstance(component.getService().getClass().getClassLoader(), capabilityInterfaces,
				new ServiceProxy(component.getService(), toggles));

		// register capabilities
		for (Class capabilityInterface : capabilityInterfaces) {
			String serviceName = capabilityInterface.getName();
			List<Dependency> dependencyList = capabilityDependencies.get(capabilityInterface);
			Properties props = new Properties();
			if (capabilityServiceProperties.get(capabilityInterface) != null) {
				props.putAll(capabilityServiceProperties.get(capabilityInterface));
			}
			Component capabilityComponent = dependencyManager.createComponent().setInterface(serviceName, props)
					.setImplementation(implementation);
			CallbacksDescription callbacksDescription = capabilityCallbacks.get(capabilityInterface);
			if (callbacksDescription != null) {
				capabilityComponent.setCallbacks(new CapabilityCallbackHandler(callbacksDescription), "initCapability", "startCapability", "stopCapability",
						"destroyCapability");
			} else {
				capabilityComponent.setCallbacks(null, null, null, null);
			}
			if (dependencyList != null) {
				// dependencies must be instance bound
				for (Dependency dependency : dependencyList) {
					if (dependency.isInstanceBound()) {
						throw new IllegalStateException("Dependencies added to capabilities must NOT be instance bound!, this dependency is not: " + dependency);
					}
				}
				capabilityComponent.add(dependencyList);
			}
			dependencyManager.add(capabilityComponent);
			capabilityComponents.add(capabilityComponent);
		}

		// passthrough of init
		if (callbackInstance == null) {
			component.invokeCallbackMethod(DependencyCallbackManager.getInstances(component), "init", new Class[][] { {}, { Component.class } }, new Object[][] {
					{}, { component } }); 
		} else {
			component.invokeCallbackMethod(new Object[] { callbackInstance }, "init", new Class[][] { {}, { Component.class } }, new Object[][] {
				{}, { component } }); 
		}
	}

	void start(Component component) {
		// passthrough of start
		if (callbackInstance == null) {
			component.invokeCallbackMethod(DependencyCallbackManager.getInstances(component), "start", new Class[][] { {}, { Component.class } }, new Object[][] {
					{}, { component } });
		} else {
			component.invokeCallbackMethod(new Object[] { callbackInstance }, "start", new Class[][] { {}, { Component.class } }, new Object[][] {
				{}, { component } }); 
		}
	}

	void stop(Component component) {
		// passthrough of stop
		if (callbackInstance == null) {
			component.invokeCallbackMethod(DependencyCallbackManager.getInstances(component), "stop", new Class[][] { {}, { Component.class } }, new Object[][] {
					{}, { component } });
		} else {
			component.invokeCallbackMethod(new Object[] { callbackInstance }, "stop", new Class[][] { {}, { Component.class } }, new Object[][] {
				{}, { component } }); 
		}			
	}

	void destroy(Component component) {
		// passthrough of destroy
		if (callbackInstance == null) {
			component.invokeCallbackMethod(DependencyCallbackManager.getInstances(component), "destroy", new Class[][] { {}, { Component.class } }, new Object[][] {
					{}, { component } });
		} else {
			component.invokeCallbackMethod(new Object[] { callbackInstance }, "destroy", new Class[][] { {}, { Component.class } }, new Object[][] {
				{}, { component } }); 
		}	
		// remove capability components from the dependency manager
//		for (Component capabilityComponent : capabilityComponents) {
//			//dependencyManager.remove(capabilityComponent);
//		}
	}
	
	void changed(Component component) {
		if (callbackInstance != null) {
			component.invokeCallbackMethod(new Object[] { callbackInstance }, "changed", new Class[][] { {}, { Component.class } }, new Object[][] {
				{}, { component } }); 
		}	
	}

	class CapabilityCallbackHandler {

		CallbacksDescription callbacksDescription;

		public CapabilityCallbackHandler(CallbacksDescription callbacksDescription) {
			this.callbacksDescription = callbacksDescription;
		}

		void initCapability(Component component) {
			if (callbacksDescription.getInit() != null) {
				component.invokeCallbackMethod(DependencyCallbackManager.getInstances(component), callbacksDescription.getInit(), new Class[][] { {},
						{ Component.class } }, new Object[][] { {}, { component } });
			}
		}

		void startCapability(Component component) {
			if (callbacksDescription.getStart() != null) {
				component.invokeCallbackMethod(DependencyCallbackManager.getInstances(component), callbacksDescription.getStart(), new Class[][] { {},
						{ Component.class } }, new Object[][] { {}, { component } });
			}
		}

		void stopCapability(Component component) {
			if (callbacksDescription.getStop() != null) {
				component.invokeCallbackMethod(DependencyCallbackManager.getInstances(component), callbacksDescription.getStop(), new Class[][] { {},
						{ Component.class } }, new Object[][] { {}, { component } });
			}
		}

		void destroyCapability(Component component) {
			if (callbacksDescription.getDestroy() != null) {
				component.invokeCallbackMethod(DependencyCallbackManager.getInstances(component), callbacksDescription.getDestroy(), new Class[][] { {},
						{ Component.class } }, new Object[][] { {}, { component } });
			}
		}
	}

}